/**
 * 
 */
package adnotatio.common.xml;

/**
 * This is a name of XML entities (elements and attributes). If two names have
 * the same namespace and the same local name then they are considered as equals
 * even if they have different prefixes. (It is not a bug, it is a feature!) If
 * two names have no namespaces then they have to have the same prefixes and
 * local names to be equals.
 * 
 * @author kotelnikov
 */
public class XMLName {

    /**
     * An empty instance of this class.
     */
    public static final XMLName EMPTY = new XMLName(null, null, "");

    /**
     * Escapes (if required) all special symbols in the given string and returns
     * the escaped value.
     * 
     * @param value the value to escape
     * @returns the given value with all escaped special symbols
     */
    static String escape(String value) {
        if (value == null)
            return null;
        StringBuffer buf = new StringBuffer();
        escape(value, buf);
        String str = buf.toString();
        return str;
    }

    /**
     * Escapes (if required) all special symbols in the given string and appends
     * the result to the given buffer
     * 
     * @param e the value to escape
     * @param buf the buffer where the escaped value should be appended
     */
    static void escape(String e, StringBuffer buf) {
        char[] array = e.toCharArray();
        for (int i = 0; i < array.length; i++) {
            char ch = array[i];
            switch (ch) {
                case '&':
                    buf.append("&amp;");
                    break;
                case '<':
                    buf.append("&lt;");
                    break;
                case '>':
                    buf.append("&gt;");
                    break;
                default:
                    buf.append(ch);
                    break;
            }
        }
    }

    /**
     * The local name of this node
     */
    private final String fLocalName;

    /**
     * The namespace of this entry
     */
    private final String fNamespace;

    /**
     * The prefix of this node
     */
    private final String fPrefix;

    /**
     * A string representation of this identifier. This value is activated by
     * the {@link #toString()} method and it should not be changed directly.
     */
    private String fStr;

    /**
     * Creates a new name with the specified local name. This constructor sets
     * other fields (namespace and namespace prefix) to <code>null</code>.
     * 
     * @param localName the local name to set
     */
    public XMLName(String localName) {
        fNamespace = null;
        fPrefix = null;
        fLocalName = escape(localName);
    }

    /**
     * @param namespace the namespace corresponding to this name
     * @param prefix the prefix of this name
     * @param localName the local name
     */
    public XMLName(String namespace, String prefix, String localName) {
        fNamespace = namespace;
        fPrefix = escape(prefix);
        fLocalName = escape(localName);
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof XMLName))
            return false;
        XMLName o = (XMLName) obj;
        if (fNamespace != null) {
            return equals(fNamespace, o.fNamespace)
                && equals(fLocalName, o.fLocalName);
        } else {
            return equals(fPrefix, o.fPrefix)
                && equals(fLocalName, o.fLocalName);
        }
    }

    /**
     * Compares the given two values and returns <code>true</code> if they are
     * equals or if both of them equal to <code>null</code>.
     * 
     * @param first the first value to compare
     * @param second the second value to compare
     * @return <code>true</code> if the given values are equal
     */
    private boolean equals(String first, String second) {
        return first != null && second != null
            ? first.equals(second)
            : first == second;
    }

    /**
     * Returns the local name
     * 
     * @return the local name
     */
    public String getLocalName() {
        return fLocalName;
    }

    /**
     * Returns the name of the tag/attribute corresponding to this object; this
     * is a cancatenated value of the prefix with the local name.
     * 
     * @return the name of the tag/attribute corresponding to this object
     */
    public String getName() {
        return fPrefix != null ? fPrefix + ":" + fLocalName : fLocalName;
    }

    /**
     * Returns the namespace
     * 
     * @return the namespace
     */
    public String getNamespace() {
        return fNamespace;
    }

    /**
     * Returns the prefix
     * 
     * @return the prefix
     */
    public String getPrefix() {
        return fPrefix;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return toString().hashCode();
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        if (fStr == null) {
            StringBuffer buf = new StringBuffer();
            if (fNamespace != null) {
                buf.append(fNamespace);
            } else if (fPrefix != null) {
                buf.append(fPrefix);
                buf.append(":");
            }
            buf.append(fLocalName);
            fStr = buf.toString();
        }
        return fStr;
    }
}
